﻿namespace NumericalData.SolvationMethods
open System
open NumericalData
open System.Diagnostics
open NumericalData.Systems
open NumericalData.Exceptions
open NumericalData.Attributes
open NumericalData.SolvationMethods
open NumericalData.SolvationMethods.Async

[<SolvationMethodAnalysisWorkName("Adaptive Simple Explicit Method")>]
type AdaptiveExplicitSolvationMethod = 
    class
        inherit TraceableIterationMethod
        new() = {inherit TraceableIterationMethod()}
        /// <summary>
        /// Solve SLAE using adaptive explicit method Xn+1 = (E - TnA)Xn + TnH
        /// Tn changes according to previous iteration mistake change
        /// <param name="sys">System to be solved</param>
        /// <param name="accur"> accurcy - simply iteration method parameter</param>
        /// <exception cref="SLAEInitialMatrixCantBeSolvedException">Matrix can't be solved with this method</exception>
        /// </summary>
        override x.Solve system accur startIsZero maxIterations = 

            let (vec,iters) = x.Iterate accur startIsZero maxIterations system.InitialMatrix system.InitialVector

            x.FormResult system vec iters accur 

        override x.Iterate accur startIsZero maxIterations startMatr startVec = 

            let matrixRate = (Rates.FrobeniusRate startMatr)
            let startTau =  1./(matrixRate)
            let E = MatrixGenerators<SquareMatrix>.IdentityMatrix startMatr.ColumnsNumber

            let rec iterationMethod (prev:Vector) (prevMistake:double) (prevTau:double) (tauDelta:double) (iteration) = 
                if Double.IsInfinity prevMistake then 
                    raise <| SLAEInitialMatrixCantBeSolvedException
                let mutable currentTau = prevTau
                let mutable currentMistake = Rates.FrobeniusRate((startMatr*prev.ToMatrix()).ToVector() - startVec)
                if (prevMistake > currentMistake) then
                    currentTau <- currentTau - tauDelta
                else
                    currentTau <- currentTau + currentTau*0.9
                let next = ((E - currentTau*startMatr)*prev.ToMatrix()).ToVector() + (currentTau*startVec)
                if Rates.FrobeniusRate(next - prev) <= accur || iteration > maxIterations then
                    (next,iteration)
                else
                    iterationMethod next currentMistake currentTau (currentTau - prevTau) (iteration + 1)

            if startIsZero then
                iterationMethod (new Vector([| for i in 0..(startVec.Length - 1) -> 0. |])) 0.0 startTau startTau 0
            else
                iterationMethod startVec 0.0 startTau startTau 0

        interface IAsyncSolvable<DiagnosticsAnswer> with 
            member x.AsyncSolve system accur startIsZero maxIterations asyncSuccess asyncFail asyncCancel = 
                let loppedSolveMethod = x.TraceSolve system accur startIsZero
                AsyncSolveFactory.Generate<int,DiagnosticsAnswer> loppedSolveMethod maxIterations asyncSuccess asyncFail asyncCancel
       
        member x.AsyncSolve  = (x :> IAsyncSolvable<DiagnosticsAnswer>).AsyncSolve
    end
